﻿/*
 *  Copyright 2009 Alessandro Pilotti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2.1 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 */

using System;
using System.Collections.Generic;
using System.Management.Automation;
using System.Text;
using AlexPilotti.L2SQLDataModel.SchemaInfo;
using AlexPilotti.L2SQLDataModel;
using System.Reflection;
using System.Security.Policy;
using System.Security;
using System.Security.Permissions;
using System.IO;

namespace AlexPilotti.PowerQuery.Commands
{
    class Worker : MarshalByRefObject
    {
        IList<Exception> errors = new List<Exception>();

        public IList<Exception> GenerateAssembly(string connectionString, string assemblyName, string assemblyDir, string ns, 
                                     bool includeTables, bool includeViews, bool includeProcedures,
                                     string snKeyPairFilePath)
        {
            IDBSchemaInfo si = MSSQLDBSchemaInfoFactory.CreateSchemaInfo(connectionString);

            try
            {
                L2SQLDataModelGenerator dcg = new L2SQLDataModelGenerator();

                si.HandleError += new DBSchemaInfoErrorEventHandler(HandleDBSchemaAndModelError);
                dcg.HandleError += new DBSchemaInfoErrorEventHandler(HandleDBSchemaAndModelError);

                dcg.GenerateAssembly(assemblyName, assemblyDir, si, ns,
                                     includeTables, includeViews, includeProcedures,
                                     snKeyPairFilePath);

                return errors;
            }
            finally
            {
                if (si is IDisposable)
                    (si as IDisposable).Dispose();
            }

        }

        void HandleDBSchemaAndModelError(object sender, DBSchemaInfoErrorEventArgs e)
        {
            errors.Add(e.Exception);
        }
    }

    [Cmdlet(VerbsCommon.New, "DataModelAssembly")]    
    public class NewDataModelAssemblyCommand : Cmdlet
    {        
        [Parameter(Position = 0, Mandatory = true)]
        [ValidateNotNullOrEmpty]
        public string ConnectionString { get; set; }

        [Parameter(Position = 1, Mandatory = true)]
        [ValidateNotNullOrEmpty]
        public string AssemblyName { get; set; }

        [Parameter(Position = 2, Mandatory = true)]
        [ValidateNotNullOrEmpty]
        public string AssemblyDir { get; set; }

        [Parameter(Position = 3)]
        [ValidateNotNullOrEmpty]
        public string Namespace { get; set; }

        [Parameter(Position = 4)]
        public bool IncludeTables { get; set; }

        [Parameter(Position = 5)]
        public bool IncludeViews { get; set; }

        [Parameter(Position = 6)]
        public bool IncludeProcedures { get; set; }        

        [Parameter(Position = 7)]
        [ValidateNotNullOrEmpty]
        public bool LoadAssembly { get; set; }

        [Parameter(Position = 8)]
        [ValidateNotNullOrEmpty]
        public string StrongNameKeyPairFilePath { get; set; }

        public NewDataModelAssemblyCommand()
        {
            LoadAssembly = true;
            Namespace = null;
            IncludeTables = true;
            IncludeViews = false;
            IncludeProcedures = false;
        }

        protected override void BeginProcessing()
        {
            base.BeginProcessing();

            // Use a separate app domain to generate the data context assembly and unload it afterwards. 
            // Doing it in the current app domain causes a lot of problems with later type loading.
            AppDomain ad = AppDomain.CreateDomain("DataModelGeneratorTemp");
            try
            {
                Worker remoteWorker = (Worker)ad.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, GetType().Namespace + ".Worker");

                IList<Exception> errors = remoteWorker.GenerateAssembly(ConnectionString, AssemblyName, AssemblyDir, Namespace, 
                                                       IncludeTables, IncludeViews, IncludeProcedures, 
                                                       StrongNameKeyPairFilePath);

                foreach(Exception ex in errors)
                    WriteError(new ErrorRecord(ex, "GenerateAssembly", ErrorCategory.InvalidOperation, null));

                // Load the assembly in the current app domain
                if (LoadAssembly)
                    Assembly.LoadFrom(Path.Combine(AssemblyDir, AssemblyName + ".dll"));
            }
            catch (FileNotFoundException ex)
            {
                throw new Exception(string.Format("Assembly \"{0}\" and any dependency must be deployed to the GAC", Assembly.GetExecutingAssembly().GetName().Name), ex);
            }
            finally
            {
                AppDomain.Unload(ad);
            }
        }
    }
}
